package com.xzq.erp.utils;

import cn.hutool.core.exceptions.ValidateException;
import cn.hutool.jwt.JWT;
import cn.hutool.jwt.JWTValidator;
import cn.hutool.jwt.signers.JWTSigner;
import cn.hutool.jwt.signers.JWTSignerUtil;
import com.xzq.erp.constants.JwtConstants;
import com.xzq.erp.context.BaseContext;
import com.xzq.erp.enums.ResponseCodeEnum;
import com.xzq.erp.exception.BizException;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.security.KeyPair;
import java.time.Duration;
import java.util.Date;
import java.util.UUID;

@Component
public class JwtTool {

    private final StringRedisTemplate stringRedisTemplate;
    private final JWTSigner jwtSigner;

    public JwtTool(StringRedisTemplate stringRedisTemplate, KeyPair keyPair) {
        this.stringRedisTemplate = stringRedisTemplate;
        this.jwtSigner = JWTSignerUtil.createSigner(JwtConstants.JWT_ALGORITHM, keyPair);
    }

    /**
     * 创建 access-token
     *
     * @param userId 用户ID
     * @return access-token
     */
    public String createToken(Long  userId) {
        // 1.生成jws
        return JWT.create()
                .setPayload(JwtConstants.PAYLOAD_USERID_KEY, userId)
                .setExpiresAt(new Date(System.currentTimeMillis() + JwtConstants.JWT_TOKEN_TTL.toMillis()))
                .setSigner(jwtSigner)
                .sign();
    }

    /**
     * 创建刷新token，并将token的JTI记录到Redis中
     *
     * @param userId 用户ID
     * @param rememberMe 是否记住我
     * @return 刷新token
     */
    public String createRefreshToken(Long userId,Boolean rememberMe) {
        // 1.生成 JTI
        String jti = UUID.randomUUID().toString();
        // 2.生成jwt
        // 2.1.如果是记住我，则有效期7天，否则30分钟
        Duration ttl = BooleanUtils.isTrue(rememberMe) ?
                JwtConstants.JWT_REMEMBER_ME_TTL : JwtConstants.JWT_REFRESH_TTL;
        // 2.2.生成token
        String token = JWT.create()
                .setJWTId(jti)
                .setPayload(JwtConstants.PAYLOAD_USERID_KEY, userId)
                .setExpiresAt(new Date(System.currentTimeMillis() + ttl.toMillis()))
                .setSigner(jwtSigner)
                .sign();
        // 3.缓存jti，有效期与token一致，过期或删除JTI后，对应的refresh-token失效
        stringRedisTemplate.opsForValue()
                .set(JwtConstants.JWT_REDIS_KEY_PREFIX + userId, jti, ttl);
        return token;
    }

    /**
     * 解析刷新token
     *
     * @param token 刷新token
     * @return 解析刷新token得到的用户ID
     */
    public Long parseToken(String token) {
        // 1.校验token是否为空
        if(StringUtils.isBlank(token)){
            throw new BizException(ResponseCodeEnum.UNAUTHORIZED);
        }
        // 2.校验并解析jwt
        JWT jwt;
        try {
            jwt = JWT.of(token).setSigner(jwtSigner);
        } catch (Exception e) {
            throw new BizException(ResponseCodeEnum.UNAUTHORIZED);
        }
        // 2.校验jwt是否有效
        if (!jwt.verify()) {
            // 验证失败
            throw new BizException(ResponseCodeEnum.UNAUTHORIZED);
        }
        // 3.校验是否过期
        try {
            JWTValidator.of(jwt).validateDate();
        } catch (ValidateException e) {
            throw new BizException(ResponseCodeEnum.UNAUTHORIZED);
        }
        // 4.数据格式校验
        Object userIdPayload = jwt.getPayload(JwtConstants.PAYLOAD_USERID_KEY);
        if (userIdPayload == null) {
            // 数据为空
            throw new BizException(ResponseCodeEnum.UNAUTHORIZED);
        }
        // 5.数据解析
        Integer userId;
        try {
            userId = (Integer) userIdPayload;
        } catch (RuntimeException e) {
            // 数据格式有误
            throw new BizException(ResponseCodeEnum.UNAUTHORIZED);
        }
        return (long) userId;
    }

    /**
     * 解析刷新token
     *
     * @param refreshToken 刷新token
     * @return 解析刷新token得到的用户ID
     */
    public Long parseRefreshToken(String refreshToken) {
        // 1.校验token是否为空
        if(StringUtils.isBlank(refreshToken)){
            throw new BizException(ResponseCodeEnum.UNAUTHORIZED);
        }
        // 2.校验并解析jwt
        JWT jwt;
        try {
            jwt = JWT.of(refreshToken).setSigner(jwtSigner);
        } catch (Exception e) {
            throw new BizException(ResponseCodeEnum.UNAUTHORIZED);
        }
        // 2.校验jwt是否有效
        if (!jwt.verify()) {
            // 验证失败
            throw new BizException(ResponseCodeEnum.UNAUTHORIZED);
        }
        // 3.校验是否过期
        try {
            JWTValidator.of(jwt).validateDate();
        } catch (ValidateException e) {
            throw new BizException(ResponseCodeEnum.UNAUTHORIZED);
        }
        // 4.数据格式校验
        Object userIdPayload = jwt.getPayload(JwtConstants.PAYLOAD_USERID_KEY);
        Object jtiPayload = jwt.getPayload(JwtConstants.PAYLOAD_JTI_KEY);
        if (jtiPayload == null || userIdPayload == null) {
            // 数据为空
            throw new BizException(ResponseCodeEnum.UNAUTHORIZED);
        }

        // 5.数据解析
        Integer userId;
        try {
            userId = (Integer) userIdPayload;
        } catch (RuntimeException e) {
            // 数据格式有误
            throw new BizException(ResponseCodeEnum.UNAUTHORIZED);
        }

        // 6.JTI校验
        String jti = stringRedisTemplate.opsForValue().get(JwtConstants.JWT_REDIS_KEY_PREFIX + userId);
        if (!StringUtils.equals(jti, jtiPayload.toString())) {
            // jti不一致
            throw new BizException(ResponseCodeEnum.UNAUTHORIZED);
        }
        return (long) userId;
    }

    /**
     * 清理刷新refresh-token的jti，本质是refresh-token作废
     */
    public void cleanJtiCache() {
        stringRedisTemplate.delete(JwtConstants.JWT_REDIS_KEY_PREFIX + BaseContext.getUserId());
    }
}